package componentes;

import java.io.IOException;

import javax.el.ELContext;
import javax.el.ELException;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.el.VariableMapper;
import javax.faces.FacesException;
import javax.faces.component.UIComponent;
import javax.faces.event.ActionEvent;

import com.sun.facelets.FaceletContext;
import com.sun.facelets.FaceletException;
import com.sun.facelets.el.VariableMapperWrapper;
import com.sun.facelets.tag.TagConfig;
import com.sun.facelets.tag.TagHandler;

public class ActionMapperTagHandler extends TagHandler {
    private static final Class<?>[] ACTION_PARAM_TYPES = new Class[0];
    private static final Class<?>[] ACTION_LISTENER_PARAM_TYPES = new Class[] {ActionEvent.class};
    private static final String ACTION = "action";
    private static final String ACTION_LISTENER = "actionListener";
    private static final String MAPPED_ACTION = "mappedAction";
    private static final String MAPPED_ACTION_LISTENER = "mappedActionListener";
	private static final MethodExpression NOOP_ACTION_EXPRESSION = null;
	private static final MethodExpression NOOP_ACTION_LISTENER_EXPRESSION = null;

    public ActionMapperTagHandler(TagConfig config) {
        super(config);
    }

    private MethodExpression remap(FaceletContext faceletContext, String varName, Class<?> expectedReturnType, Class<?>[] expectedParamTypes) {
        MethodExpression result = null;
        
        VariableMapper mapper = faceletContext.getVariableMapper();
        ValueExpression valueExpression = mapper.resolveVariable(varName);

        if (valueExpression != null) {
            ExpressionFactory ef = faceletContext.getExpressionFactory();
            ELContext elContext = faceletContext.getFacesContext().getELContext();            

            result = ef.createMethodExpression(elContext, valueExpression.getExpressionString(), expectedReturnType, expectedParamTypes);
        }        

        return result;
    }    

    public void apply(FaceletContext ctx, UIComponent parent) throws IOException, FacesException, FaceletException, ELException {
        MethodExpression actionExpression = remap(ctx, ACTION, String.class, ACTION_PARAM_TYPES);
        MethodExpression actionListenerExpression = remap(ctx, ACTION_LISTENER, null, ACTION_LISTENER_PARAM_TYPES);        

        if (actionExpression != null || actionListenerExpression != null) {
            VariableMapper initialVarMapper = ctx.getVariableMapper();

            try {
                VariableMapperWrapper varMapper = new VariableMapperWrapper(initialVarMapper);            

                if (actionExpression == null) {
                    actionExpression = NOOP_ACTION_EXPRESSION;
                }                

                varMapper.setVariable(MAPPED_ACTION, ctx.getExpressionFactory().createValueExpression(actionExpression, MethodExpression.class));

                if (actionListenerExpression == null) {
                    actionListenerExpression = NOOP_ACTION_LISTENER_EXPRESSION;
                }

                varMapper.setVariable(MAPPED_ACTION_LISTENER, ctx.getExpressionFactory().createValueExpression(actionListenerExpression, MethodExpression.class));
                ctx.setVariableMapper(varMapper);
                nextHandler.apply(ctx, parent);
            } finally {
                ctx.setVariableMapper(initialVarMapper);
            }
        } else {
            nextHandler.apply(ctx, parent);
        }
    }
}